/*
 * ivi_xmit.c :
 *  IVI Packet Transmission Kernel Module
 * 
 * by haoyu@cernet.edu.cn 2008.10.22
 *
 * Changes:
 *	Wentao Shang	:	Remove multicast functionality and implement skbuff re-enter.
 *	Wentao Shang	:	Simplified prefix matching and address translation, v4network and v6prefix are now hard coded.
 *	Wentao Shang	:	Add NAT44-PT support.
 *	Wentao Shang	:	Add ICMP translation support.
 *	Wentao Shang	:	IPv6 prefix length is configurable now.
 *	Wentao Shang	:	Add rule lookup to address translation routine.
 *	Wentao Shang	:	Add core translation mode support.
 *	Wentao Shang	:	Changed to IVI44 mode.
 */

#include "ivi_xmit.h"

// public address for v4 network
__be32 pub_addr = 0x03030000;  // "3.3.0.0/16" in host byte order

static inline int addr_in_v4network(const unsigned int addr) {
	return ((ntohl(addr) & 0xff000000) == 0x0a000000);  // '10.0.0.0/8'
}

static inline int addr_in_v4pub(const unsigned int addr) {
	return ((ntohl(addr) & 0xffff0000) == (pub_addr & 0xffff0000));
}

int ivi_v4v6_xmit(struct sk_buff *skb) {
	struct iphdr *ip4h;
	struct tcphdr *tcph;
	struct udphdr *udph;
	struct icmphdr *icmph;
	__u8 *payload;
	__be32 oaddr, naddr;
	u16 newp, offset;
	unsigned int plen;

	ip4h = ip_hdr(skb);
	if (ipv4_is_multicast(ip4h->daddr) || ipv4_is_lbcast(ip4h->daddr) || ipv4_is_loopback(ip4h->daddr)) {
		// By pass multicast packet
		//printk(KERN_ERR "ivi_v4v6_xmit: by pass ipv4 multicast/broadcast/loopback dest address.\n");
		return NF_ACCEPT;
	}

	if (addr_in_v4network(ip4h->daddr)) {
		// Do not translate ipv4 packets (hair pin) that are toward v4network.
		//printk(KERN_ERR "ivi_v4v6_xmit: by pass ipv4 packet sent toward the v4 network.\n");
		return NF_ACCEPT;
	}

	payload = (__u8 *)(ip4h) + (ip4h->ihl << 2);
	plen = htons(ip4h->tot_len) - (ip4h->ihl * 4);

	/* src addr translate */
	oaddr = ntohl(ip4h->saddr);
	offset = oaddr & 0xff;  // private addr has the form of '10.c.d.offset'
	naddr = (pub_addr & 0xffff0000) + ((oaddr >> 8) & 0xffff);  // public addr is formed as 'a.b.c.d'
	ip4h->saddr = htonl(naddr);
	csum_replace4(&ip4h->check, htonl(oaddr), ip4h->saddr);

	/* src port translate */
	switch (ip4h->protocol) {
		case IPPROTO_TCP:
			tcph = (struct tcphdr *)payload;
			
			if (get_outflow_tcp_map_port(oaddr, ntohs(tcph->source), hgw_ratio, hgw_adjacent, offset, tcph, plen, &newp) == -1) {
				printk(KERN_ERR "ivi_v4v6_xmit: fail to perform nat44 mapping for " NIP4_FMT ":%d (TCP).\n", NIP4(oaddr), ntohs(tcph->source));
				// Just let the packet pass with original address.
			} else {
				csum_replace4(&tcph->check, htonl(oaddr), ip4h->saddr);
				newp = htons(newp);
				csum_replace2(&tcph->check, tcph->source, newp);
				tcph->source = newp;
			}

			break;

		case IPPROTO_UDP:
			udph = (struct udphdr *)payload;

			if (get_outflow_map_port(&udp_list, oaddr, ntohs(udph->source), hgw_ratio, hgw_adjacent, offset, &newp) == -1) {
				printk(KERN_ERR "ivi_v4v6_xmit: fail to perform nat44 mapping for " NIP4_FMT ":%d (UDP).\n", NIP4(oaddr), ntohs(udph->source));
				// Just let the packet pass with original address.
			} else {
				csum_replace4(&udph->check, htonl(oaddr), ip4h->saddr);
				newp = htons(newp);
				csum_replace2(&udph->check, udph->source, newp);
				udph->source = newp;
			}

			break;

		case IPPROTO_ICMP:
			icmph = (struct icmphdr *)payload;

			if (icmph->type == ICMP_ECHO || icmph->type == ICMP_ECHOREPLY) {
				if (get_outflow_map_port(&icmp_list, oaddr, ntohs(icmph->un.echo.id), hgw_ratio, hgw_adjacent, offset, &newp) == -1) {
					printk(KERN_ERR "ivi_v4v6_xmit: fail to perform nat44 mapping for " NIP4_FMT ":%d (ICMP).\n", NIP4(oaddr), ntohs(icmph->un.echo.id));
					// Just let the packet pass with original address.
				} else {
					newp = htons(newp);
					csum_replace2(&icmph->checksum, icmph->un.echo.id, newp);
					icmph->un.echo.id = newp;
				}
			} else {
				printk(KERN_ERR "ivi_v4v6_xmit: unsupported ICMP type in nat44. Drop packet now.\n");
				return NF_DROP;
			}

			break;

		default:
			printk(KERN_ERR "ivi_v4v6_xmit: by pass unsupported protocol %d in nat44.\n", ip4h->protocol);
	}

	return NF_ACCEPT;
}


static inline u16 get_offset(u16 port) {
	return ((port / hgw_adjacent) % hgw_ratio);
}

int ivi_v6v4_xmit(struct sk_buff *skb) {
	struct iphdr *ip4h;
	struct tcphdr *tcph;
	struct udphdr *udph;
	struct icmphdr *icmph;
	__u8 *payload;
	__be32 oaddr;
	u16 offset, newp, oldp;
	unsigned int plen;

	ip4h = ip_hdr(skb);
	if (!addr_in_v4pub(ip4h->daddr)) {
		// Do not translate ipv4 packets that are not toward v4 public network.
		//printk(KERN_ERR "ivi_v6v4_xmit: by pass ipv4 packet not toward the v4 public network.\n");
		return NF_ACCEPT;
	}

	payload = (__u8 *)(ip4h) + (ip4h->ihl << 2);
	plen = htons(ip4h->tot_len) - (ip4h->ihl * 4);

	/* dst addr & port translation */
	switch (ip4h->protocol) {
		case IPPROTO_TCP:
			tcph = (struct tcphdr *)payload;
			newp = ntohs(tcph->dest);
			offset = get_offset(newp);
			oaddr = 0x0a000000 + ((ntohl(ip4h->daddr) << 8) & 0x00ffff00) + (offset & 0xff);  // '10.c.d.offset'

			if (get_inflow_tcp_map_port(oaddr, newp, tcph, plen, &oldp) == -1) {
				//printk(KERN_ERR "ivi_v6v4_xmit: fail to perform nat44 mapping for %d (TCP).\n", ntohs(tcph->dest));
				return NF_DROP;
			} else {
				/* dst addr translate */
				oaddr = htonl(oaddr);
				csum_replace4(&ip4h->check, ip4h->daddr, oaddr);
				csum_replace4(&tcph->check, ip4h->daddr, oaddr);
				ip4h->daddr = oaddr;
				oldp = htons(oldp);
				csum_replace2(&tcph->check, tcph->dest, oldp);
				tcph->dest = oldp;
			}

			break;

		case IPPROTO_UDP:
			udph = (struct udphdr *)payload;
			newp = ntohs(udph->dest);
			offset = get_offset(newp);
			oaddr = 0x0a000000 + ((ntohl(ip4h->daddr) << 8) & 0x00ffff00) + (offset & 0xff);  // '10.c.d.offset'

			if (get_inflow_map_port(&udp_list,  oaddr, newp, &oldp) == -1) {
				//printk(KERN_ERR "ivi_v6v4_xmit: fail to perform nat44 mapping for %d (UDP).\n", ntohs(udph->dest));
				return NF_DROP;
			} else {
				/* dst addr translate */
				oaddr = htonl(oaddr);
				csum_replace4(&ip4h->check, ip4h->daddr, oaddr);
				csum_replace4(&udph->check, ip4h->daddr, oaddr);
				ip4h->daddr = oaddr;
				oldp = htons(oldp);
				csum_replace2(&udph->check, udph->dest, oldp);
				udph->dest = oldp;
			}

			break;

		case IPPROTO_ICMP:
			icmph = (struct icmphdr *)payload;
			newp = ntohs(icmph->un.echo.id);
			offset = get_offset(newp);
			oaddr = 0x0a000000 + ((ntohl(ip4h->daddr) << 8) & 0x00ffff00) + (offset & 0xff);  // '10.c.d.offset'

			if (icmph->type == ICMP_ECHO || icmph->type == ICMP_ECHOREPLY) {
				if (get_inflow_map_port(&icmp_list, oaddr, newp, &oldp) == -1) {
					//printk(KERN_ERR "ivi_v6v4_xmit: fail to perform nat44 mapping for %d (ICMP).\n", ntohs(icmph->un.echo.id));
					return NF_DROP;
				} else {
					/* dst addr translate */
					oaddr = htonl(oaddr);
					csum_replace4(&ip4h->check, ip4h->daddr, oaddr);
					ip4h->daddr = oaddr;
					oldp = htons(oldp);
					csum_replace2(&icmph->checksum, icmph->un.echo.id, oldp);
					icmph->un.echo.id = oldp;
				}
			} else {
				printk(KERN_ERR "ivi_v6v4_xmit: unsupported ICMPv6 type in xlate. Drop packet.\n");
				return NF_DROP;
			}

			break;

		default:
			printk(KERN_ERR "ivi_v6v4_xmit: by pass unsupported protocol %d in nat44.\n", ip4h->protocol);
	}
	
	return NF_ACCEPT;
}
